package middleware.p2p;

import java.io.File;
import java.io.StringReader;
import java.util.Random;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.stream.StreamSource;

import middleware.p2p.log.MiddlewareLogEvent;
import middleware.p2p.log.MiddlewareMonitorEvent;
import middleware.p2p.log.VectorClock;

import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 *	Classe che implementa un Worker.
 *	Il worker è mappato su un thread che estrae i task dalla coda e li esegue.
 */
public class Worker  extends Thread implements AbstractWorker {
	/**
	 * 	Descrittore contenente i dati sul Worker
	 */
	private WorkerDescriptor descriptor;
	/**
	 * 	Coda in cui vengono inseriti i TaskDescriptor contenenti i dati sui Task da eseguire.
	 */
	private TQueueManager queue;
	/**
	 * 	Address del Worker.
	 */
	private Address addr;
	/**
	 * 	Costruttore di default del Worker.
	 * 	Il costruttore carica i dati sull'IP e inizializza la coda.
	 **/
	public Worker() {
		Logger log=Logger.getLogger(this.getClass());		
		WorkerDescriptor desc=new WorkerDescriptor();
		addr=new Address();
		try {
			File file = new File("peer_config.xml");
			Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(file);
			doc.getDocumentElement().normalize();
			NodeList nodeLst = doc.getElementsByTagName("peer_config");
			for (int i = 0; i < nodeLst.getLength(); i++) {
				Node tNode=nodeLst.item(i);
				if (tNode.getNodeType()==Node.ELEMENT_NODE) {
					Element config=(Element) tNode;
					Element peerIp=(Element)(config.getElementsByTagName("ip").item(0));
					addr.setIP(((Node)peerIp.getChildNodes().item(0)).getNodeValue());
					Element peerPort=(Element)(config.getElementsByTagName("port").item(0));
					addr.setPort(Integer.parseInt(((Node)peerPort.getChildNodes().item(0)).getNodeValue()));
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
			log.error(e,e);
			System.exit(0);
		}
		desc.setAddr(addr);		
		this.descriptor=desc;
		log.debug("Worker created on "+this.descriptor.getAddr().getIP()+":"+this.descriptor.getAddr().getPort());
		this.queue=new TQueueManager();		
	}
	/**
	 * @return the addr
	 */
	public Address getAddr() {
		return addr;
	}
	@Override
	public int exec(TaskDescriptor ts) {
		Random rnd=new Random();
		try {
			VectorClock localClock=CORE.getLogVectorClock().addEventToClock(CORE.getComponentId());
			Logger.getLogger(this.getClass()).info(new MiddlewareLogEvent(localClock,
					"Dummy execution of task "+ts.getID()+" : "+ ts.getCommand()));
			Thread.sleep(900+rnd.nextInt(200));
		} catch (InterruptedException ie) {}
		return 0;
	}
	public void run() {
		Logger log=Logger.getLogger(this.getClass());		
		VectorClock logLocalClock=CORE.getLogVectorClock().addEventToClock(CORE.getComponentId());		
		log.info(new MiddlewareLogEvent(logLocalClock,
				"Worker started on "+this.descriptor.getAddr().getIP()+":"+this.descriptor.getAddr().getPort()));
		/*****************************************************/
		String replica=CORE.getReplicaFrontend().getReplica();
		log.debug("Collecting Replica...");
		try {
			JAXBContext cont=JAXBContext.newInstance(TQueueManager.class);
			Unmarshaller um=cont.createUnmarshaller();
			JAXBElement<TQueueManager> jElem=um.unmarshal(new StreamSource(new StringReader(replica)),TQueueManager.class);
			TQueueManager replicatedObj=jElem.getValue();
			for(TaskDescriptor task:replicatedObj.getTQueue()) {
				log.debug("Enqueue TaskDescriptor #"+task.getID()+" taken from the Replica");
				this.queue.enqueue(task);
			}
		} catch (JAXBException je) {
			log.error(je);
		}		
		/*****************************************************/
		TaskQueueServerThread serverThread=new TaskQueueServerThread(this.queue, this.descriptor.getAddr().getPort());
		serverThread.start();
		while(true) {
			log.debug("Waiting for dequeueing  a TaskDescriptor...");
			MiddlewareMonitorEvent monitorEvent=new MiddlewareMonitorEvent();
			TaskDescriptor task=this.queue.dequeue(monitorEvent);			
			logLocalClock=CORE.getLogVectorClock().addEventToClock(CORE.getComponentId());
			VectorClock monitorLocalClock=CORE.getMonitorVectorClock().addEventToClock(CORE.getComponentId());
			log.info(new MiddlewareLogEvent(logLocalClock,"Task "+task.getID()+" extracted"));
			monitorEvent.setVclock(monitorLocalClock);
	        monitorEvent.setComponentID(CORE.getComponentId());
	        Logger.getLogger(CORE.monitorTDQueueName).info(monitorEvent.getQueueContent());
	        Logger.getLogger(CORE.monitorTDQueueStatusName).info(monitorEvent);
	        CORE.getReplicaFrontend().storeReplica(monitorEvent.getQueueContent().toXmlString());
			this.exec(task);
		}
	}	
}